/****************************************************************************************/
/* MediaFrame is an Open Source streaming media platform in Java                        */
/* which provides a fast, easy to implement and extremely small applet                  */
/* that enables to view your audio/video content without having                         */
/* to rely on external player applications or bulky plug-ins.                           */
/*                                                                                      */
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* We changed a lot of code and added a lot of functionality.							*/
/* This includes, but not limited to, the following changes:							*/
/* 1. The project was renamed to MediaFrame;											*/
/* 2. The connection speed detection procedure was added;								*/
/* 3. The JavaScript API functions were added;											*/
/* 4. The pre and post image support functionality was added;							*/
/* 5. The ability to save movie into the local disk was added;							*/
/* 6. The inner buffer for a movie file was added;										*/
/* 7. The click-through functionality was added;    									*/
/* 8. The .zip files support was added;    												*/
/* 9. The realtime feedback agent functionality was added.    							*/
/* For the full list of the current functionality please visit the following web page:	*/
/* http://mediaframe.org/																*/
/*    																					*/
/* 06 Jul 2002 - 19 Dec 2004 Konstantin Belous, Oleg Lebedev							*/
/*																						*/
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* "MPEG_Reader.java"                                                                   */
/*                                                                                      */
/* This class has replaced the MPEG_scan-class which was the parser in 'SurePlayer'.    */
/* The class consists of two buffers, one for video and one for audio. It fills these   */
/* buffers with help of the CutElement (video) and AudioElement (audio) classes. If     */
/* a buffer is to full (meaning he can't keep up) elements can then be thrown away      */
/* so that the player will skip some video or audio. (handy with LIVE streams)          */
/*                                                                                      */
/* -- Ron -- (29-04-2002) r_breukelaar@hotmail.com                                      */
/*                                                                                      */
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* This program is free software; you can redistribute it and/or modify it under the    */
/* terms of the GNU General Public License as published by the Free Software            */
/* Foundation; either version 2 of the License, or (at your option) any later version.  */
/*                                                                                      */
/* This program is distributed in the hope that it will be useful, but WITHOUT ANY      */
/* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      */
/* PARTICULAR PURPOSE. See the GNU General Public License for more details.             */
/*                                                                                      */
/* You should have received a copy of the GNU General Public License along with this    */
/* program; (See "LICENSE.GPL"). If not, write to the Free Software Foundation, Inc.,   */
/* 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.                            */
/*                                                                                      */
/*--------------------------------------------------------------------------------------*/
/*                                                                                      */
/* If the program runs as Java applet it isn't "interactive" in the sense of the GNU    */
/* General Public License. So paragraph 2c doesn't apply.                               */
/*                                                                                      */
/****************************************************************************************/

package mediaframe.mpeg1;

import java.io.*;
import java.util.*;
import mediaframe.mpeg1.audio.player.*;

/** 
 * The <code>MPEG_Reader</code> class consists of two buffers, one for video and 
 * one for audio. It starts the "MPEG_Reader" thread and fills these buffers. 
 * It starts rebuffering and calls endOfBuffer method of the applet after the
 * (re-)buffering process is finished.
 */
public class MPEG_Reader implements Runnable {

	/** 4 bytes mask (0xffffffff). */	
	public static final long MASK = (255L << 24) + (255L << 16) + (255L << 8) + 255L;

	/** The number of bytes loaded into the buffer. */
	public int loadBytes = 0;
	
	/** Constanta, the "buffering" state of the reader. */
	public static final int BUFFERING = 0;
	/** Constanta, the "re-buffering" state of the reader. */
	public static final int REBUFFERING = 1;
	/** Constanta, the "reading" state of the reader. */
	public static final int READING = 2;
	/** Constanta, the "closed" state of the reader. */
	public static final int CLOSED = 3;
	
	/** Current state of the reader. */
	private int state = BUFFERING;
	
	/** The number of bytes that should be put into the buffer before the movie playback could start. */
	public int maxBufferSize;
	/** The minimum number of bytes in the buffer before the "re-buffering" starts. */ 
	public int minBufferSize;
	/** The number of bytes that should be put into the buffer during the "rebuffering" process. */
	public int reBufferingSize = 0;
	/** Equals to the initial value of the <code>maxBufferSize</code>. */
	private int initBufferSize;

	/** The length of the movie file. */
	public int contentLength;

	/** Last printed "(re)-buffering" percent value. */
	public int printedPercent = 0;
	/** Last time when the "(re)-buffering" percent was printed.*/
	public long lastPrintedTime = -1;

	/** The reference to the main applet object. */	
	public MPEG1 videoPlayer = null;

	/** The <code>DataBuffer</code> for audio stream. */
	private DataBuffer audioBuffer = null;
	/** The <code>DataBuffer</code> for video stream. */
	private DataBuffer videoBuffer  = null;

	/** The 4 bytes buffer for the input data. */
	private long _long = -1;
	/** <tt>True</tt> if the end of the movie has been reached. */
	private boolean _eof = false;
	/** The number of threads which waits the writer side. */
	private int _waiting = 0;

	/** The "MPEG_Reader" thread object. */
	private volatile Thread _thread;

	/** The input stream with the movie data. */
	private InputStream _in;

	/** The time stamp of the last readed frame. */
	private long _read_time = -1;

	/**
	 * Constructs a <code>MPEG_Reader</code> object with the specified parameters 
	 * and starts the "MPEG_Reader" thread.
	 * 
	 * @param videoPlayer   the reference to the main applet object.
	 * @param in            the input stream with the movie data.
	 * @param maxBufferSize the number of bytes that should be put into the buffer 
	 *                      before the movie playback could start.
	 * @param minBufferSize the minimum number of bytes in the buffer before the "re-buffering" starts.
	 * @param contentLength the length of the movie file.
	 */
	public MPEG_Reader(
			MPEG1 videoPlayer,
			InputStream in, 
			int maxBufferSize,
			int minBufferSize,
			int contentLength
		) {

		super();

		this.videoPlayer = videoPlayer;
		this.contentLength = contentLength;
		this.maxBufferSize = this.initBufferSize = maxBufferSize;
		this.minBufferSize = minBufferSize;
		
		this.videoBuffer = new DataBuffer(io_tool.TYPE_VIDEO, this);
		this.audioBuffer = new DataBuffer(io_tool.TYPE_AUDIO, this);

/*		
		videoBuffer.setSynchroBuffer(audioBuffer);
		audioBuffer.setSynchroBuffer(videoBuffer);
*/
		_in = new BufferedInputStream(in);

                if (_in == null)
                        System.out.println("INPUTSTREAM == NULL !!");

		_thread = new Thread(this, "MPEG_Reader");
		_thread.start();
	}

	/**
	 * Reads the current frame (audio or video) into the buffer.
	 * @throws Exception if an error occurs.
	 */
	public void read_element() throws Exception {

		VideoFrame frame = new VideoFrame(_read_time, videoBuffer, audioBuffer);

		videoBuffer.registerNewFrame(frame);

		_long = frame.fill(_in, _long);

		if (_long != -1) {

			if (frame.getTimeStamp() > -1) {
				_read_time = frame.getTimeStamp();
			}

			videoBuffer.saveCurrentFrame();

			notify_reader();

		} else {
			_eof = true;

			videoBuffer.saveLastFrame();
			
			close();
			
			System.out.println("End of file reached...");
			notify_reader();
		}
	}

	/**
	 * Reads one byte from the input stream into 4 bytes buffer.
	 * @throws Exception if an error occurs.
	 */
	public void read_to_long() throws Exception {

		int r = _in.read();
		if (r==-1)
			_eof = true;

		_long = ((_long << 8) & MASK) | r;
	}

	/**
	 * Start point and main loop of the "MPEG_Reader" thread.<p/>
	 * Calls the method that reads the frames until the end of the movie file is reached or this thread is stopped.
	 */
	public void run()
	{
//		Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
		try
		{
			read_to_long();
			read_to_long();
			read_to_long();
			read_to_long();

			while (!_eof && _thread != null)
			{
				read_element();
                Thread.yield();
			}
		}
		catch (InterruptedException ex) {
		}
		catch(Exception e)
		{
                        System.out.println("MPEG_Reader: "+e);
			e.printStackTrace(System.out);
		}
			
		close();
		
		_thread = null;
		moribund = null;

		System.out.println("MPEG_Reader Thread is closed!");
	}

	/**
	 * Waits the writer for the next block of data.
	 * @throws InterruptedException if another process interrupts this process.
	 */
	public synchronized void wait_for_read() throws InterruptedException {

		if(!_eof) {
			_waiting ++;
			wait();
			_waiting --;
		}
	}

	/**
	 * Notifies the reader side about the next portion of data.
	 */
	public synchronized void notify_reader() {

		if (_waiting > 0)
			notifyAll();
	}

	/** The "MPEG_Reader" thread that is being stopped. */
	private volatile Thread moribund = null;

	/**
	 * Stops the "MPEG_Reader" thread.
	 */
	public void stop() {
		// System.out.println("Try to stop the Thread MPEG_Reader!");

		if (_thread != null)
		{
			moribund = _thread;
			_thread = null;
			moribund.interrupt();
		}		
	}

	/**
	 * Waits until the "MPEG_Reader" thread is dead.
	 */
	public void join() {
			// Wait until MPEG_Reader is dead
			if( moribund != null){
				try { moribund.join(); } catch (InterruptedException ex) { System.out.println("strange---interrupted"); }
			}
	}

	/**
	 * Closes the input stream.
	 */
	public void close() {
		if(state == REBUFFERING) {
			if(videoPlayer != null)
				videoPlayer.endRebuffering(this);
		} else if(state == BUFFERING) {
			if(videoPlayer != null)
				videoPlayer.playerEndBuffer(this);
		}
		state = CLOSED;
		
		try {
			if(_in != null) {
				_in.close();
				_in = null;
			}
		} catch (Throwable tr) {}
	}

	/**
	 * Invoked when the reader ends buffering and is ready to play the movie.
	 */
	public void endOfBuffering() {
		if(state != CLOSED) {
			state = READING;
			if(videoPlayer != null)
				videoPlayer.playerEndBuffer(this);
		}
	}
	
	/**
	 * Starts the "re-buffering" process.
	 */
	public void startRebuffering() {
		if(state == READING) {
			state = REBUFFERING;
			printedPercent = 0;
			maxBufferSize = loadBytes;
			maxBufferSize += initBufferSize;
			if(contentLength > 0 && maxBufferSize > contentLength) {
				maxBufferSize = contentLength;
			}
			reBufferingSize = maxBufferSize - loadBytes;
			if(videoPlayer != null)
				videoPlayer.startRebuffering(this);
		}
	}

	/**
	 * Invoked when the reader ends the "re-buffering" process and is ready to play the movie.
	 */
	public void endOfReBuffering() {
		if(state == REBUFFERING) {
			state = READING;
			if(videoPlayer != null)
				videoPlayer.endRebuffering(this);
		}
	}

	/**
	 * Gets the audio buffer instance.
	 * @return the audio buffer instance.
	 */
	public DataBuffer getAudioBuffer() {
		return audioBuffer;
	}

	/**
	 * Gets the video buffer instance.
	 * @return the video buffer instance.
	 */
	public DataBuffer getVideoBuffer() {
		return videoBuffer;
	}

	/**
	 * Returns <tt>true</tt> if the end of the movie has been reached.
	 * @return <tt>true</tt> if the end of the movie has been reached.
	 */	
	public boolean isEOF() {
		return _eof;
	}
	
	/**
	 * Returns the state of the reader.
	 * @return current state of the reader.
	 */
	public int getState() {
		return state;
	}


}
